#include <cstdio>
#include <cstring>
#include <iostream>
#include "FPodReader.h"

using namespace std;


/**
 * TODOC
 */
FPodReader::FPodReader()
{
  m_podFile = NULL;
  m_currentBuf = NULL;
  m_currentBufPos = 0;
  m_currentBufLen = 0;
}


/**
 * TODOC
 */
FPodReader::~FPodReader()
{
  this->closePod();
}


/**
 * TODOC
 */
bool FPodReader::openPod(const string& podPath)
{
  this->closePod();

  m_podFile = unzOpen(podPath.c_str());
  return (m_podFile != NULL);
}


/**
 * TODOC
 */
void FPodReader::closePod() {
  if (m_podFile)
  {
    unzClose(m_podFile);
    m_podFile = NULL;
  }
  delete[] m_currentBuf;
  m_currentBuf = NULL;
  m_currentFile = string();
}


/**
 * TODOC
 */
bool FPodReader::setCurrentFile(const string& filename)
{
  delete[] m_currentBuf;

  if (m_podFile && (unzLocateFile(m_podFile, filename.c_str(), false) == UNZ_OK))
  {
    unz_file_info fileInfo;
    unzGetCurrentFileInfo(m_podFile, &fileInfo, NULL, 0, NULL, 0, NULL, 0);
    m_currentFile = filename;
    m_currentBufPos = 0;
    m_currentBufLen = fileInfo.uncompressed_size;
    m_currentBuf = new uint8_t[m_currentBufLen];
    unzOpenCurrentFile(m_podFile);
    unzReadCurrentFile(m_podFile, m_currentBuf, m_currentBufLen);
    unzCloseCurrentFile(m_podFile);
    return true;
  }
  else
  {
    m_currentBuf = NULL;
    m_currentFile = string();
    return false;
  }
}


/**
 * TODOC
 */
const string& FPodReader::getCurrentFile()
{
  return m_currentFile;
}


/**
 * TODOC
 */
vector<string> FPodReader::listFCodeFiles()
{
  vector<string> fcodeFiles;

  if (m_podFile)
  {
    unz_global_info podInfo;
    unzGetGlobalInfo(m_podFile, &podInfo);
    int numFiles = podInfo.number_entry;

    unzGoToFirstFile(m_podFile);
    for (int i = 0; i < numFiles; i++)
    {
      char filename[FILENAME_MAX];
      unzGetCurrentFileInfo(m_podFile, NULL, filename, sizeof(filename), NULL, 0, NULL, 0);
      int filenameLen = strlen(filename);
      if ((filenameLen > 6) && (strcmp(filename + filenameLen - 6, ".fcode") == 0))
        fcodeFiles.push_back(filename);

      if ((i + 1) < numFiles)
        unzGoToNextFile(m_podFile);
    }
  }

  return fcodeFiles;
}


/**
 * TODOC
 */
uint8_t FPodReader::readUInt8()
{
  if (m_currentBufPos <= m_currentBufLen - 1)
    return m_currentBuf[m_currentBufPos++];
  else
    throw ios_base::failure("Trying to read after the end of the file");
}


/**
 * TODOC
 */
uint16_t FPodReader::readUInt16()
{
  if (m_currentBufPos <= m_currentBufLen - 2)
  {
    uint16_t byte1 = m_currentBuf[m_currentBufPos++];
    uint16_t byte2 = m_currentBuf[m_currentBufPos++];
    return ((byte1 << 8) | byte2);
  }
  else
    throw ios_base::failure("Trying to read after the end of the file");
}


/**
 * TODOC
 */
uint32_t FPodReader::readUInt32()
{
  if (m_currentBufPos <= m_currentBufLen - 4)
  {
    uint32_t byte1 = m_currentBuf[m_currentBufPos++];
    uint32_t byte2 = m_currentBuf[m_currentBufPos++];
    uint32_t byte3 = m_currentBuf[m_currentBufPos++];
    uint32_t byte4 = m_currentBuf[m_currentBufPos++];
    return ((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | byte4);
  }
  else
    throw ios_base::failure("Trying to read after the end of the file");
}


/**
 * TODOC
 */
uint64_t FPodReader::readUInt64()
{
  if (m_currentBufPos <= m_currentBufLen - 8)
  {
    uint64_t byte1 = m_currentBuf[m_currentBufPos++];
    uint64_t byte2 = m_currentBuf[m_currentBufPos++];
    uint64_t byte3 = m_currentBuf[m_currentBufPos++];
    uint64_t byte4 = m_currentBuf[m_currentBufPos++];
    uint64_t byte5 = m_currentBuf[m_currentBufPos++];
    uint64_t byte6 = m_currentBuf[m_currentBufPos++];
    uint64_t byte7 = m_currentBuf[m_currentBufPos++];
    uint64_t byte8 = m_currentBuf[m_currentBufPos++];
    return ((byte1 << 56) | (byte2 << 48) | (byte3 << 40) | (byte4 << 32)
        | (byte5 << 24) | (byte6 << 16) | (byte7 << 8) | byte8);
  }
  else
    throw ios_base::failure("Trying to read after the end of the file");
}


/**
 * TODOC
 */
int8_t FPodReader::readInt8()
{
  if (m_currentBufPos <= m_currentBufLen - 1)
    return m_currentBuf[m_currentBufPos++];
  else
    throw ios_base::failure("Trying to read after the end of the file");
}


/**
 * TODOC
 */
int16_t FPodReader::readInt16()
{
  if (m_currentBufPos <= m_currentBufLen - 2)
  {
    int16_t byte1 = m_currentBuf[m_currentBufPos++];
    int16_t byte2 = m_currentBuf[m_currentBufPos++];
    return ((byte1 << 8) | byte2);
  }
  else
    throw ios_base::failure("Trying to read after the end of the file");
}


/**
 * TODOC
 */
int32_t FPodReader::readInt32()
{
  if (m_currentBufPos <= m_currentBufLen - 4)
  {
    int32_t byte1 = m_currentBuf[m_currentBufPos++];
    int32_t byte2 = m_currentBuf[m_currentBufPos++];
    int32_t byte3 = m_currentBuf[m_currentBufPos++];
    int32_t byte4 = m_currentBuf[m_currentBufPos++];
    return ((byte1 << 24) | (byte2 << 16) | (byte3 << 8) | byte4);
  }
  else
    throw ios_base::failure("Trying to read after the end of the file");
}


/**
 * TODOC
 */
int64_t FPodReader::readInt64()
{
  if (m_currentBufPos <= m_currentBufLen - 8)
  {
    int64_t byte1 = m_currentBuf[m_currentBufPos++];
    int64_t byte2 = m_currentBuf[m_currentBufPos++];
    int64_t byte3 = m_currentBuf[m_currentBufPos++];
    int64_t byte4 = m_currentBuf[m_currentBufPos++];
    int64_t byte5 = m_currentBuf[m_currentBufPos++];
    int64_t byte6 = m_currentBuf[m_currentBufPos++];
    int64_t byte7 = m_currentBuf[m_currentBufPos++];
    int64_t byte8 = m_currentBuf[m_currentBufPos++];
    return ((byte1 << 56) | (byte2 << 48) | (byte3 << 40) | (byte4 << 32)
        | (byte5 << 24) | (byte6 << 16) | (byte7 << 8) | byte8);
  }
  else
    throw ios_base::failure("Trying to read after the end of the file");
}


/**
 * TODOC
 */
float FPodReader::readFloat()
{
  uint32_t intVal = this->readUInt32();
  return *((float*)&intVal);
}


/**
 * TODOC
 */
double FPodReader::readDouble()
{
  uint64_t intVal = this->readUInt64();
  return *((double*)&intVal);
}


/**
 * TODOC
 */
string FPodReader::readString()
{
  uint16_t len = this->readUInt16();
  if (m_currentBufPos <= m_currentBufLen - len)
  {
    //TODO: support unicode here
    string str((char*)&m_currentBuf[m_currentBufPos], len);
    m_currentBufPos += len;
    return str;
  }
  else
    throw ios_base::failure("Trying to read after the end of the file");
}
